/*
 * Copyright (c) 2007-2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: IncrementalSAXSource_Xerces.java,v 1.2.4.1 2005/09/15 08:15:08 suresh_emailid Exp $
 */

package com.sun.org.apache.xml.internal.dtm.ref;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import com.sun.org.apache.xerces.internal.parsers.SAXParser;
import com.sun.org.apache.xml.internal.res.XMLErrorResources;
import com.sun.org.apache.xml.internal.res.XMLMessages;
import com.sun.org.apache.xalan.internal.utils.ObjectFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/** <p>IncrementalSAXSource_Xerces takes advantage of the fact that Xerces1
 * incremental mode is already a coroutine of sorts, and just wraps our
 * IncrementalSAXSource API around it.</p>
 *
 * <p>Usage example: See main().</p>
 *
 * <p>Status: Passes simple main() unit-test. NEEDS JAVADOC.</p>
 * */
public class IncrementalSAXSource_Xerces implements IncrementalSAXSource {
    //
    // Reflection. To allow this to compile with both Xerces1 and Xerces2, which
    // require very different methods and objects, we need to avoid static
    // references to those APIs. So until Xerces2 is pervasive and we're willing
    // to make it a prerequisite, we will rely upon relection.
    //
    Method fParseSomeSetup = null; // Xerces1 method
    Method fParseSome = null; // Xerces1 method
    Object fPullParserConfig = null; // Xerces2 pull control object
    Method fConfigSetInput = null; // Xerces2 method
    Method fConfigParse = null; // Xerces2 method
    Method fSetInputSource = null; // Xerces2 pull control method
    Constructor fConfigInputSourceCtor = null; // Xerces2 initialization method
    Method fConfigSetByteStream = null; // Xerces2 initialization method
    Method fConfigSetCharStream = null; // Xerces2 initialization method
    Method fConfigSetEncoding = null; // Xerces2 initialization method
    Method fReset = null; // Both Xerces1 and Xerces2, but diff. signatures

    //
    // Data
    //
    SAXParser fIncrementalParser;
    private boolean fParseInProgress = false;

    //
    // Constructors
    //

    /** Create a IncrementalSAXSource_Xerces, and create a SAXParser
     * to go with it. Xerces2 incremental parsing is only supported if
     * this constructor is used, due to limitations in the Xerces2 API (as of
     * Beta 3). If you don't like that restriction, tell the Xerces folks that
     * there should be a simpler way to request incremental SAX parsing.
     * */
    public IncrementalSAXSource_Xerces() throws NoSuchMethodException {
        try {
            // Xerces-2 incremental parsing support (as of Beta 3)
            // ContentHandlers still get set on fIncrementalParser (to get
            // conversion from XNI events to SAX events), but
            // _control_ for incremental parsing must be exercised via the config.
            //
            // At this time there's no way to read the existing config, only
            // to assert a new one... and only when creating a brand-new parser.
            //
            // Reflection is used to allow us to continue to compile against
            // Xerces1. If/when we can abandon the older versions of the parser,
            // this will simplify significantly.

            // If we can't get the magic constructor, no need to look further.
            Class xniConfigClass = ObjectFactory.findProviderClass("com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration", true);
            Class[] args1 = { xniConfigClass };
            Constructor ctor = SAXParser.class.getConstructor(args1);

            // Build the parser configuration object. StandardParserConfiguration
            // happens to implement XMLPullParserConfiguration, which is the API
            // we're going to want to use.
            Class xniStdConfigClass = ObjectFactory.findProviderClass("com.sun.org.apache.xerces.internal.parsers.StandardParserConfiguration", true);
            fPullParserConfig = xniStdConfigClass.newInstance();
            Object[] args2 = { fPullParserConfig };
            fIncrementalParser = (SAXParser) ctor.newInstance(args2);

            // Preload all the needed the configuration methods... I want to know they're
            // all here before we commit to trying to use them, just in case the
            // API changes again.
            Class fXniInputSourceClass = ObjectFactory.findProviderClass("com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource", true);
            Class[] args3 = { fXniInputSourceClass };
            fConfigSetInput = xniStdConfigClass.getMethod("setInputSource", args3);

            Class[] args4 = { String.class, String.class, String.class };
            fConfigInputSourceCtor = fXniInputSourceClass.getConstructor(args4);
            Class[] args5 = { java.io.InputStream.class };
            fConfigSetByteStream = fXniInputSourceClass.getMethod("setByteStream", args5);
            Class[] args6 = { java.io.Reader.class };
            fConfigSetCharStream = fXniInputSourceClass.getMethod("setCharacterStream", args6);
            Class[] args7 = { String.class };
            fConfigSetEncoding = fXniInputSourceClass.getMethod("setEncoding", args7);

            Class[] argsb = { Boolean.TYPE };
            fConfigParse = xniStdConfigClass.getMethod("parse", argsb);
            Class[] noargs = new Class[0];
            fReset = fIncrementalParser.getClass().getMethod("reset", noargs);
        } catch (Exception e) {
            // Fallback if this fails (implemented in createIncrementalSAXSource) is
            // to attempt Xerces-1 incremental setup. Can't do tail-call in
            // constructor, so create new, copy Xerces-1 initialization,
            // then throw it away... Ugh.
            IncrementalSAXSource_Xerces dummy = new IncrementalSAXSource_Xerces(new SAXParser());
            this.fParseSomeSetup = dummy.fParseSomeSetup;
            this.fParseSome = dummy.fParseSome;
            this.fIncrementalParser = dummy.fIncrementalParser;
        }
    }

    /** Create a IncrementalSAXSource_Xerces wrapped around
     * an existing SAXParser. Currently this works only for recent
     * releases of Xerces-1.  Xerces-2 incremental is currently possible
     * only if we are allowed to create the parser instance, due to
     * limitations in the API exposed by Xerces-2 Beta 3; see the
     * no-args constructor for that code.
     *
     * @exception if the SAXParser class doesn't support the Xerces
     * incremental parse operations. In that case, caller should
     * fall back upon the IncrementalSAXSource_Filter approach.
     * */
    public IncrementalSAXSource_Xerces(SAXParser parser) throws NoSuchMethodException {
        // Reflection is used to allow us to compile against
        // Xerces2. If/when we can abandon the older versions of the parser,
        // this constructor will simply have to fail until/unless the
        // Xerces2 incremental support is made available on previously
        // constructed SAXParser instances.
        fIncrementalParser = parser;
        Class me = parser.getClass();
        Class[] parms = { InputSource.class };
        fParseSomeSetup = me.getMethod("parseSomeSetup", parms);
        parms = new Class[0];
        fParseSome = me.getMethod("parseSome", parms);
        // Fallback if this fails (implemented in createIncrementalSAXSource) is
        // to use IncrementalSAXSource_Filter rather than Xerces-specific code.
    }

    //
    // Factories
    //
    static public IncrementalSAXSource createIncrementalSAXSource() {
        try {
            return new IncrementalSAXSource_Xerces();
        } catch (NoSuchMethodException e) {
            // Xerces version mismatch; neither Xerces1 nor Xerces2 succeeded.
            // Fall back on filtering solution.
            IncrementalSAXSource_Filter iss = new IncrementalSAXSource_Filter();
            iss.setXMLReader(new SAXParser());
            return iss;
        }
    }

    static public IncrementalSAXSource createIncrementalSAXSource(SAXParser parser) {
        try {
            return new IncrementalSAXSource_Xerces(parser);
        } catch (NoSuchMethodException e) {
            // Xerces version mismatch; neither Xerces1 nor Xerces2 succeeded.
            // Fall back on filtering solution.
            IncrementalSAXSource_Filter iss = new IncrementalSAXSource_Filter();
            iss.setXMLReader(parser);
            return iss;
        }
    }

    //
    // Public methods
    //

    // Register handler directly with the incremental parser
    public void setContentHandler(org.xml.sax.ContentHandler handler) {
        // Typecast required in Xerces2; SAXParser doesn't inheret XMLReader
        // %OPT% Cast at asignment?
        ((XMLReader) fIncrementalParser).setContentHandler(handler);
    }

    // Register handler directly with the incremental parser
    public void setLexicalHandler(org.xml.sax.ext.LexicalHandler handler) {
        // Not supported by all SAX2 parsers but should work in Xerces:
        try {
            // Typecast required in Xerces2; SAXParser doesn't inheret XMLReader
            // %OPT% Cast at asignment?
            ((XMLReader) fIncrementalParser).setProperty("http://xml.org/sax/properties/lexical-handler", handler);
        } catch (org.xml.sax.SAXNotRecognizedException e) {
            // Nothing we can do about it
        } catch (org.xml.sax.SAXNotSupportedException e) {
            // Nothing we can do about it
        }
    }

    // Register handler directly with the incremental parser
    public void setDTDHandler(org.xml.sax.DTDHandler handler) {
        // Typecast required in Xerces2; SAXParser doesn't inheret XMLReader
        // %OPT% Cast at asignment?
        ((XMLReader) fIncrementalParser).setDTDHandler(handler);
    }

    //================================================================
    /** startParse() is a simple API which tells the IncrementalSAXSource
     * to begin reading a document.
     *
     * @throws SAXException is parse thread is already in progress
     * or parsing can not be started.
     * */
    public void startParse(InputSource source) throws SAXException {
        if (fIncrementalParser == null)
            throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_STARTPARSE_NEEDS_SAXPARSER, null)); //"startParse needs a non-null SAXParser.");
        if (fParseInProgress)
            throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_STARTPARSE_WHILE_PARSING, null)); //"startParse may not be called while parsing.");

        boolean ok = false;

        try {
            ok = parseSomeSetup(source);
        } catch (Exception ex) {
            throw new SAXException(ex);
        }

        if (!ok)
            throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_COULD_NOT_INIT_PARSER, null)); //"could not initialize parser with");
    }

    /** deliverMoreNodes() is a simple API which tells the coroutine
     * parser that we need more nodes.  This is intended to be called
     * from one of our partner routines, and serves to encapsulate the
     * details of how incremental parsing has been achieved.
     *
     * @param parsemore If true, tells the incremental parser to generate
     * another chunk of output. If false, tells the parser that we're
     * satisfied and it can terminate parsing of this document.
     * @return Boolean.TRUE if the CoroutineParser believes more data may be available
     * for further parsing. Boolean.FALSE if parsing ran to completion.
     * Exception if the parser objected for some reason.
     * */
    public Object deliverMoreNodes(boolean parsemore) {
        if (!parsemore) {
            fParseInProgress = false;
            return Boolean.FALSE;
        }

        Object arg;
        try {
            boolean keepgoing = parseSome();
            arg = keepgoing ? Boolean.TRUE : Boolean.FALSE;
        } catch (SAXException ex) {
            arg = ex;
        } catch (IOException ex) {
            arg = ex;
        } catch (Exception ex) {
            arg = new SAXException(ex);
        }
        return arg;
    }

    // Private methods -- conveniences to hide the reflection details
    private boolean parseSomeSetup(InputSource source) throws SAXException, IOException, IllegalAccessException, java.lang.reflect.InvocationTargetException, java.lang.InstantiationException {
        if (fConfigSetInput != null) {
            // Obtain input from SAX inputSource object, construct XNI version of
            // that object. Logic adapted from Xerces2.
            Object[] parms1 = { source.getPublicId(), source.getSystemId(), null };
            Object xmlsource = fConfigInputSourceCtor.newInstance(parms1);
            Object[] parmsa = { source.getByteStream() };
            fConfigSetByteStream.invoke(xmlsource, parmsa);
            parmsa[0] = source.getCharacterStream();
            fConfigSetCharStream.invoke(xmlsource, parmsa);
            parmsa[0] = source.getEncoding();
            fConfigSetEncoding.invoke(xmlsource, parmsa);

            // Bugzilla5272 patch suggested by Sandy Gao.
            // Has to be reflection to run with Xerces2
            // after compilation against Xerces1. or vice
            // versa, due to return type mismatches.
            Object[] noparms = new Object[0];
            fReset.invoke(fIncrementalParser, noparms);

            parmsa[0] = xmlsource;
            fConfigSetInput.invoke(fPullParserConfig, parmsa);

            // %REVIEW% Do first pull. Should we instead just return true?
            return parseSome();
        } else {
            Object[] parm = { source };
            Object ret = fParseSomeSetup.invoke(fIncrementalParser, parm);
            return ((Boolean) ret).booleanValue();
        }
    }

    //  Would null work???
    private static final Object[] noparms = new Object[0];
    private static final Object[] parmsfalse = { Boolean.FALSE };

    private boolean parseSome() throws SAXException, IOException, IllegalAccessException, java.lang.reflect.InvocationTargetException {
        // Take next parsing step, return false iff parsing complete:
        if (fConfigSetInput != null) {
            Object ret = (Boolean) (fConfigParse.invoke(fPullParserConfig, parmsfalse));
            return ((Boolean) ret).booleanValue();
        } else {
            Object ret = fParseSome.invoke(fIncrementalParser, noparms);
            return ((Boolean) ret).booleanValue();
        }
    }

    //================================================================
    /** Simple unit test. Attempt coroutine parsing of document indicated
     * by first argument (as a URI), report progress.
     */
    public static void _main(String args[]) {
        System.out.println("Starting...");

        CoroutineManager co = new CoroutineManager();
        int appCoroutineID = co.co_joinCoroutineSet(-1);
        if (appCoroutineID == -1) {
            System.out.println("ERROR: Couldn't allocate coroutine number.\n");
            return;
        }
        IncrementalSAXSource parser = createIncrementalSAXSource();

        // Use a serializer as our sample output
        com.sun.org.apache.xml.internal.serialize.XMLSerializer trace;
        trace = new com.sun.org.apache.xml.internal.serialize.XMLSerializer(System.out, null);
        parser.setContentHandler(trace);
        parser.setLexicalHandler(trace);

        // Tell coroutine to begin parsing, run while parsing is in progress

        for (int arg = 0; arg < args.length; ++arg) {
            try {
                InputSource source = new InputSource(args[arg]);
                Object result = null;
                boolean more = true;
                parser.startParse(source);
                for (result = parser.deliverMoreNodes(more); result == Boolean.TRUE; result = parser.deliverMoreNodes(more)) {
                    System.out.println("\nSome parsing successful, trying more.\n");

                    // Special test: Terminate parsing early.
                    if (arg + 1 < args.length && "!".equals(args[arg + 1])) {
                        ++arg;
                        more = false;
                    }

                }

                if (result instanceof Boolean && ((Boolean) result) == Boolean.FALSE) {
                    System.out.println("\nParser ended (EOF or on request).\n");
                } else if (result == null) {
                    System.out.println("\nUNEXPECTED: Parser says shut down prematurely.\n");
                } else if (result instanceof Exception) {
                    throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException((Exception) result);
                    //          System.out.println("\nParser threw exception:");
                    //          ((Exception)result).printStackTrace();
                }

            }

            catch (SAXException e) {
                e.printStackTrace();
            }
        }

    }

} // class IncrementalSAXSource_Xerces
